package sanmubird.club.v8.Stream;

import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * 本文 介绍 流的基本操作：
 * 过滤
 * 切片
 * 映射
 * 查找 匹配
 * 归约
 * 复合操作
 * 无状态 和 有状态 map 和 filter 是无状态的 但 sort[无界] 和 distinct[无界] limit skip  reduce 是 有状态的 他需要知道之前的历史记录
 * 原始类型特化流
 * 数值流
 */
public class StreamPractise {

	private Stream<Dish> stream;

	@Before
	public void setUp() {
		stream = Stream.of(
				new Dish("pork", false, 800, Dish.Type.MEAT),
				new Dish("beef", false, 700, Dish.Type.MEAT),
				new Dish("chicken", false, 400, Dish.Type.MEAT),
				new Dish("french fries", true, 530, Dish.Type.OTHER),
				new Dish("rice", true, 350, Dish.Type.OTHER),
				new Dish("season fruit", true, 120, Dish.Type.OTHER),
				new Dish("pizza", true, 550, Dish.Type.OTHER),
				new Dish("prawns", false, 300, Dish.Type.FISH),
				new Dish("prawns", false, 300, Dish.Type.FISH),
				new Dish("salmon", false, 450, Dish.Type.FISH));
	}

	/**
	 * 过滤流
	 */
	@Test
	public void filter() {
		Predicate<Dish> predicate1 = dish -> dish.getCalories() <= 350;
		Predicate<Dish> predicate2 = dish -> dish.getType().equals(Dish.Type.FISH);
//		Predicate<Dish> predicate = predicate1.and(predicate2).negate();
		Predicate<Dish> predicate = predicate1.or(predicate2);
		// 按条件筛选 并 去重
		List<Dish> collect = stream.filter(predicate).distinct().collect(Collectors.toList());
		System.out.println(collect);
	}

	/**
	 * 切片 截断 limit 跳过 skip
	 */
	@Test
	public void split() {
		Predicate<Dish> predicate = dish -> dish.getCalories() >= 350;
		// 取三个 跳过2个
		List<Dish> collect = stream.filter(predicate).limit(3).skip(2).collect(Collectors.toList());
		System.out.println(collect);
	}


	/**
	 * 映射 跟转换类似 但区别在于 不是修改 而是 创建一个新版本
	 * map() 和 flatMap() 里面是 function 把 A -> 映射 成 B
	 */
	@Test
	public void map() {
		Predicate<Dish> predicate = dish -> dish.getCalories() >= 350;
		Function<Dish, List<String>> function = dish -> Arrays.asList(dish.getName().split(""));
		stream.filter(predicate).limit(3).map(function).distinct().forEach(System.out::println);
	}

	/**
	 * 使用 map 会 生成 各个流
	 * 使用 flatMap 会将 各个流 合并起来 扁平化 成一个 流
	 */
	@Test
	public void flatMap() {
		Predicate<Dish> predicate = dish -> dish.getCalories() >= 350;
		Function<Dish, Stream<String>> function = dish -> Stream.of(dish.getName().split(""));
		stream.filter(predicate).limit(3).flatMap(function).distinct().forEach(System.out::print);
	}


	@Test
	public void flatMapTest2() {
		Stream<Integer> stream1 = Stream.of(1, 2, 3);
		List<List<Integer>> list = stream1.flatMap(i -> Stream.of(3, 4).map(j -> Arrays.asList(i, j))).collect(Collectors.toList());
		System.out.println(list);
	}

	@Test
	public void flatMapTest3() {
		Stream<Integer> stream1 = Stream.of(1, 2, 3);
//		Predicate<List<Integer>> predicate = list -> list.stream().mapToInt(i -> i).sum() % 3 == 0;
		Predicate<List<Integer>> predicate = list -> list.stream().reduce(0, (a, b) -> a + b) % 3 == 0;
		List<List<Integer>> list = stream1.flatMap(i -> Stream.of(3, 4).map(j -> Arrays.asList(i, j))).filter(predicate).collect(Collectors.toList());
		System.out.println(list);
	}

	/**
	 * 匹配
	 */
	@Test
	public void anyMatch() {
		if (stream.anyMatch(Dish::isVegetarian)) {
			System.out.println("The menu is (somewhat) vegetarian friendly!!");
		}
	}

	@Test
	public void allMatch() {
		if (stream.allMatch(d -> d.getCalories() < 1000)) {
			System.out.println("The menu is (somewhat) vegetarian friendly!!");
		}
	}

	@Test
	public void noneMatch() {
		if (stream.noneMatch(d -> d.getCalories() >= 1000)) {
			System.out.println("The menu is (somewhat) vegetarian friendly!!");
		}
	}

	@Test
	public void findAny() {
		stream.filter(Dish::isVegetarian).findAny().ifPresent(System.out::println);
	}

	@Test
	public void findFirst() {
		stream.filter(Dish::isVegetarian).findFirst().ifPresent(System.out::println);
	}

	/**
	 * 归约方法的优势与并行化：
	 * 使用 reduce 的好处在于，这里的跌代被内部跌代抽象掉了，这使内部实现得以选择并行执行 reduce 操作
	 * 而跌代式求和例子要更新共享变量 sum ， 这不是那么容易并行化的。
	 * 如果你加入了同步，很可能会发现线程竞争抵消了并行本应带来的性能提升！
	 * 这种计算的并行化需要另一种办法：将输入分块，分块求和，最后再合并起来。但这样的话代码看起来就完全不一样了。
	 */
	@Test
	public void reduceSum1() {
		// 求和 可以 任意 设定初始值
		Stream<Integer> stream1 = Stream.of(1, 2, 3);
		Integer sum = stream1.reduce(4, (a, b) -> a + b);
		System.out.println(sum);
	}

	@Test
	public void reduceSum2() {
		Stream<Integer> stream1 = Stream.of(1, 2, 3);
		// 没有 初始值 返回 一个 Optional 对象
		Optional<Integer> sum = stream1.reduce((a, b) -> (a + b));
		sum.ifPresent(System.out::println);
	}

	@Test
	public void reduceMax() {
		Stream<Integer> stream1 = Stream.of(1, 2, 2);
		Optional<Integer> sum = stream1.reduce(Integer::max);
		sum.ifPresent(System.out::println);
	}

	@Test
	public void reduceMin() {
		Stream<Integer> stream1 = Stream.of(1, 1, 2);
		Optional<Integer> sum = stream1.reduce(Integer::min);
		sum.ifPresent(System.out::println);
	}

	/**
	 * 虽然 下面两种写法 都正确
	 * 但第一种写法 暗含一个 装箱成本 每个 Integer 都必须拆箱成一个原始类型，再进行求和。
	 * Java 8引入了三个原始类型特化流接口来解决这个问题： IntStream 、 DoubleStream 和 LongStream ，分别将流中的元素特化为 int 、 long 和 double ，从而避免了?含的装箱成本。
	 * 每个接口都带来了进行常用数值??的新方法，比如对数值流求和的 sum ，找到最大元素的 max 。
	 * 此外还有在必要时再把它们转换回对象流的方法。
	 */
	@Test
	public void originalStream() {
		Stream<Integer> stream1 = Stream.of(1, 2, 3);
//		int sum = stream1.reduce(0, (a, b) -> a + b);
		int sum = stream1.mapToInt(i -> i).sum();
		System.out.println(sum);
	}

	/**
	 * range 与 rangeClosed 的最大区别是 range 不包括 ， rangeClosed 包括
	 */
	@Test
	public void rangeTest() {
		long count = IntStream.range(1, 100).filter(n -> n % 2 == 0).count();
		System.out.println(count);
	}

	@Test
	public void rangeClosedTest() {
		long count = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0).count();
		System.out.println(count);
	}

	/**
	 * peek 是为了 debugger 使用的 从这里 你也能看到 stream的执行方式
	 */
	@Test
	public void peek() {
		int sum = IntStream.of(1, 2, 3, 4)
				.filter(e -> e > 2)
				.peek(e -> System.out.println("Filtered value: " + e))
				.map(e -> e * e)
				.peek(e -> System.out.println("Mapped value: " + e))
				.sum();
		System.out.println(sum);
	}

	@Test
	public void createStreamFromOf() {
		Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
		stream.map(String::toUpperCase).forEach(System.out::println);
	}

	@Test
	public void createStreamFromArray() {
		int[] numbers = {1, 2, 3, 4};
		int sum = Arrays.stream(numbers).sum();
		System.out.println(sum);
	}

	@Test
	public void createStreamFromFile() {
		String path = new File(ClassLoader.getSystemResource("").getPath()).getPath();
		try (Stream<String> lines = Files.lines(Paths.get(path, "data.txt"), Charset.defaultCharset());) {
			List<Integer> collect = lines.map(String::length).collect(Collectors.toList());
			System.out.println(collect);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	@Test
	public void createStreamFromIterate() {
		Stream.iterate(0, n -> n + 2)
				.limit(3)
				.forEach(System.out::println);
		// 斐波那契 元祖 序列
		Stream.iterate(new int[]{0, 1},
				t -> new int[]{t[1], t[0] + t[1]})
				.limit(3)
				.forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
	}

	@Test
	public void createStreamFromGenerate() {
		Stream.generate(Math::random)
				.limit(2)
				.forEach(System.out::println);
	}


}
